Add webviewer batching#293
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 2b628e3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@proofkit/better-auth
@proofkit/fmdapi
@proofkit/fmodata
@proofkit/typegen
@proofkit/webviewer
commit: |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds opt-in batching to ChangesWebViewerAdapter batching and adapter-level pagination
Sequence Diagram(s)sequenceDiagram
participant App
participant DataApi
participant WebViewerAdapter
participant fmFetch
App->>DataApi: listAll() / findAll() / list() / find()
DataApi->>WebViewerAdapter: normalized request
alt adapter pagination fast path
WebViewerAdapter->>fmFetch: bounded read request
fmFetch-->>WebViewerAdapter: page with foundCount
WebViewerAdapter->>fmFetch: follow-up bounded reads
fmFetch-->>WebViewerAdapter: additional pages
else batch-enabled request
WebViewerAdapter->>WebViewerAdapter: enqueue and flush
WebViewerAdapter->>fmFetch: batch envelope
fmFetch-->>WebViewerAdapter: batched responses
end
WebViewerAdapter-->>DataApi: GetResponse
DataApi-->>App: records
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/fmdapi/src/client.ts (1)
179-192: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract named FileMaker dateformat codes.
The
0/1/2mapping is domain-specific and the@ts-expect-errorhides the string conversion. Named constants make the adapter payload clearer and keep the normalization local.Proposed refactor
+ const FILEMAKER_DATE_FORMAT_US = "0"; + const FILEMAKER_DATE_FORMAT_FILE_LOCALE = "1"; + const FILEMAKER_DATE_FORMAT_ISO8601 = "2"; + function normalizeFindRequest(args: FindMethodArgs): { fetch?: RequestInit; ignoreEmptyResult: boolean; params: FindOptions["data"]; timeout?: number; @@ if ("sort" in params && params.sort !== undefined && !Array.isArray(params.sort)) { params.sort = [params.sort]; } + let normalizedDateformats: string | undefined; if ("dateformats" in params && params.dateformats !== undefined) { - let dateFormatValue: number; + normalizedDateformats = FILEMAKER_DATE_FORMAT_US; if (params.dateformats === "US") { - dateFormatValue = 0; + normalizedDateformats = FILEMAKER_DATE_FORMAT_US; } else if (params.dateformats === "file_locale") { - dateFormatValue = 1; + normalizedDateformats = FILEMAKER_DATE_FORMAT_FILE_LOCALE; } else if (params.dateformats === "ISO8601") { - dateFormatValue = 2; - } else { - dateFormatValue = 0; + normalizedDateformats = FILEMAKER_DATE_FORMAT_ISO8601; } - // `@ts-expect-error` FM wants a string, so this is fine - params.dateformats = dateFormatValue.toString(); } return { fetch, ignoreEmptyResult, - params: { ...params, query } as FindOptions["data"], + params: { + ...params, + ...(normalizedDateformats === undefined ? {} : { dateformats: normalizedDateformats }), + query, + } as FindOptions["data"], timeout, }; }As per coding guidelines, "
**/*.{js,jsx,ts,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/fmdapi/src/client.ts` around lines 179 - 192, The dateformat normalization in the client adapter uses magic numbers and an inline ts-expect-error, so extract the FileMaker codes into descriptive constants and keep the conversion logic readable. Update the dateformat handling in client.ts around the params.dateformats normalization to use named symbols for the US, file_locale, and ISO8601 values, and keep the string conversion localized in that branch so the payload shape remains unchanged while the meaning is explicit.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/fmdapi/src/client.ts`:
- Around line 179-192: The dateformat normalization in the client adapter uses
magic numbers and an inline ts-expect-error, so extract the FileMaker codes into
descriptive constants and keep the conversion logic readable. Update the
dateformat handling in client.ts around the params.dateformats normalization to
use named symbols for the US, file_locale, and ISO8601 values, and keep the
string conversion localized in that branch so the payload shape remains
unchanged while the meaning is explicit.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 28af687b-89fd-4633-ab6f-be86ce9c8230
📒 Files selected for processing (7)
.changeset/quick-bats-queue.mdapps/docs/content/docs/webviewer/fmdapi.mdxpackages/fmdapi/src/adapters/core.tspackages/fmdapi/src/client.tspackages/fmdapi/tests/client-methods.test.tspackages/webviewer/src/adapter.tspackages/webviewer/tests/adapter.test.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/webviewer/src/adapter.ts (1)
145-149: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winGuard empty
messagesbefore readingcode.
resp.messages?.[0].codestill dereferences.codewhenmessagesis an empty array, so malformed FileMaker envelopes reject with aTypeErrorinstead of the intendedFileMakerError.Proposed fix
private handleDataApiResponse(resp: clientTypes.RawFMResponse): unknown { - if (resp.messages?.[0].code !== "0") { + const code = resp.messages?.[0]?.code ?? "500"; + if (code !== "0") { throw new FileMakerError( - resp?.messages?.[0].code ?? "500", - `Filemaker Data API failed with (${resp.messages?.[0].code}): ${JSON.stringify(resp, null, 2)}`, + code, + `Filemaker Data API failed with (${code}): ${JSON.stringify(resp, null, 2)}`, ); }As per coding guidelines: “Use optional chaining (
?.) and nullish coalescing (??) for safer property access.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/webviewer/src/adapter.ts` around lines 145 - 149, The FileMaker response check in handleDataApiResponse still reads code from resp.messages?.[0] without guarding the case where messages is an empty array, which can throw a TypeError before FileMakerError is raised. Update the resp.messages validation to safely handle both missing and empty messages using optional chaining and nullish coalescing, and keep the FileMakerError construction in handleDataApiResponse as the single failure path when the first message code is not "0" or is unavailable.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/webviewer/src/adapter.ts`:
- Around line 271-280: The legacy fallback in executeUnbatchedRequests currently
runs queued requests concurrently with Promise.all, which can reorder
create/update/delete side effects relative to the original batch order. Change
the flow in executeUnbatchedRequests to process each QueuedBatchRequest
sequentially in array order, awaiting one request before starting the next,
while still resolving or rejecting each request individually. Keep the behavior
inside executeSingleRequest unchanged and preserve the existing
request.resolve/request.reject handling.
---
Outside diff comments:
In `@packages/webviewer/src/adapter.ts`:
- Around line 145-149: The FileMaker response check in handleDataApiResponse
still reads code from resp.messages?.[0] without guarding the case where
messages is an empty array, which can throw a TypeError before FileMakerError is
raised. Update the resp.messages validation to safely handle both missing and
empty messages using optional chaining and nullish coalescing, and keep the
FileMakerError construction in handleDataApiResponse as the single failure path
when the first message code is not "0" or is unavailable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: aa5df4bb-b6d3-4376-8262-5e3c8cfd4ebf
📒 Files selected for processing (6)
.changeset/tiny-lamps-tap.mdapps/docs/content/docs/webviewer/batching.mdxapps/docs/content/docs/webviewer/fmdapi.mdxapps/docs/content/docs/webviewer/meta.jsonpackages/webviewer/src/adapter.tspackages/webviewer/tests/adapter.test.ts
✅ Files skipped from review due to trivial changes (3)
- apps/docs/content/docs/webviewer/meta.json
- .changeset/tiny-lamps-tap.md
- apps/docs/content/docs/webviewer/batching.mdx
| private async executeUnbatchedRequests(requests: QueuedBatchRequest[]) { | ||
| await Promise.all( | ||
| requests.map(async (request) => { | ||
| try { | ||
| request.resolve(await this.executeSingleRequest(request.payload)); | ||
| } catch (error) { | ||
| request.reject(error); | ||
| } | ||
| }), | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserve request order during legacy fallback.
When batching is unsupported, Promise.all(requests.map(...)) replays queued requests concurrently. Since the same queue can contain create, update, and delete, this can reorder side effects compared with the batch request order.
Proposed fix
private async executeUnbatchedRequests(requests: QueuedBatchRequest[]) {
- await Promise.all(
- requests.map(async (request) => {
- try {
- request.resolve(await this.executeSingleRequest(request.payload));
- } catch (error) {
- request.reject(error);
- }
- }),
- );
+ for (const request of requests) {
+ try {
+ request.resolve(await this.executeSingleRequest(request.payload));
+ } catch (error) {
+ request.reject(error);
+ }
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private async executeUnbatchedRequests(requests: QueuedBatchRequest[]) { | |
| await Promise.all( | |
| requests.map(async (request) => { | |
| try { | |
| request.resolve(await this.executeSingleRequest(request.payload)); | |
| } catch (error) { | |
| request.reject(error); | |
| } | |
| }), | |
| ); | |
| private async executeUnbatchedRequests(requests: QueuedBatchRequest[]) { | |
| for (const request of requests) { | |
| try { | |
| request.resolve(await this.executeSingleRequest(request.payload)); | |
| } catch (error) { | |
| request.reject(error); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/webviewer/src/adapter.ts` around lines 271 - 280, The legacy
fallback in executeUnbatchedRequests currently runs queued requests concurrently
with Promise.all, which can reorder create/update/delete side effects relative
to the original batch order. Change the flow in executeUnbatchedRequests to
process each QueuedBatchRequest sequentially in array order, awaiting one
request before starting the next, while still resolving or rejecting each
request individually. Keep the behavior inside executeSingleRequest unchanged
and preserve the existing request.resolve/request.reject handling.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/webviewer/src/adapter.ts (2)
251-271: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winKeep internal batch IDs authoritative.
{ id: request.id, ...request.payload }lets a top-levelidfrombodyoverwrite the queue id, causing response mapping to miss the result. Putidlast; usingfor...ofalso matches the TS iteration guideline.Proposed fix
- const resp = await fmFetch<BatchScriptResponse>(this.scriptName, { + const batchRequests: Array<DataApiScriptRequest & { id: string }> = []; + for (const request of requests) { + batchRequests.push({ + ...request.payload, + id: request.id, + }); + } + + const resp = await fmFetch<BatchScriptResponse>(this.scriptName, { batch: true, - requests: requests.map((request) => ({ - id: request.id, - ...request.payload, - })), + requests: batchRequests, });As per coding guidelines,
**/*.{ts,tsx}: “Preferfor...ofloops over other iteration methods.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/webviewer/src/adapter.ts` around lines 251 - 271, In the batch request construction inside the adapter’s fmFetch call, the queued request id is being overwritten by request.payload, which can break response mapping. Update the request object assembly so the internal queue id remains authoritative by ensuring the id field is applied last, and switch the requests mapping in this batch path to a for...of-based build to follow the TS iteration guideline.Source: Coding guidelines
224-230: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy liftKeep new arrivals on their own batch window.
drainBatchQueue()keeps looping until the queue is empty, so requests enqueued while the currentfmFetchis in flight can be sent immediately when that call returns, before theirwindowMstimer expires. Snapshot the queue for the current flush, and let later arrivals flush via their own timer/max-size path.Also applies to: 237-245
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/webviewer/src/adapter.ts` around lines 224 - 230, The batching logic in batchFlushInProgress/flushBatchQueue/drainBatchQueue is reusing the live queue, which lets requests added during an in-flight fmFetch get flushed too early. Change the flush path to snapshot the current batch at the start of flushBatchQueue and have drainBatchQueue process only that snapshot, so later arrivals remain queued and wait for their own windowMs timer or max-size trigger before sending.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/webviewer/src/adapter.ts`:
- Around line 251-271: In the batch request construction inside the adapter’s
fmFetch call, the queued request id is being overwritten by request.payload,
which can break response mapping. Update the request object assembly so the
internal queue id remains authoritative by ensuring the id field is applied
last, and switch the requests mapping in this batch path to a for...of-based
build to follow the TS iteration guideline.
- Around line 224-230: The batching logic in
batchFlushInProgress/flushBatchQueue/drainBatchQueue is reusing the live queue,
which lets requests added during an in-flight fmFetch get flushed too early.
Change the flush path to snapshot the current batch at the start of
flushBatchQueue and have drainBatchQueue process only that snapshot, so later
arrivals remain queued and wait for their own windowMs timer or max-size trigger
before sending.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c8253b42-aafa-4b06-9c95-b2c45145b37b
📒 Files selected for processing (8)
.changeset/quick-bats-queue.mdapps/docs/content/docs/webviewer/batching.mdxapps/docs/content/docs/webviewer/fmdapi.mdxpackages/fmdapi/src/adapters/core.tspackages/fmdapi/src/client.tspackages/fmdapi/tests/client-methods.test.tspackages/webviewer/src/adapter.tspackages/webviewer/tests/adapter.test.ts
✅ Files skipped from review due to trivial changes (3)
- apps/docs/content/docs/webviewer/fmdapi.mdx
- .changeset/quick-bats-queue.md
- apps/docs/content/docs/webviewer/batching.mdx
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/fmdapi/src/adapters/core.ts
- packages/fmdapi/tests/client-methods.test.ts
- packages/webviewer/tests/adapter.test.ts
- packages/fmdapi/src/client.ts
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/webviewer/src/adapter.ts (1)
200-205: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winUse safe optional chaining for malformed message arrays.
messages?.[0].codestill dereferences.codewhenmessagesis an empty array, so malformed script/batch responses throwTypeErrorinstead of the intendedFileMakerError.Proposed fix
- if (resp.messages?.[0].code !== "0") { + const messageCode = resp.messages?.[0]?.code; + if (messageCode !== "0") { throw new FileMakerError( - resp?.messages?.[0].code ?? "500", - `Filemaker Data API failed with (${resp.messages?.[0].code}): ${JSON.stringify(resp, null, 2)}`, + messageCode ?? "500", + `Filemaker Data API failed with (${messageCode}): ${JSON.stringify(resp, null, 2)}`, ); }As per coding guidelines, use optional chaining (
?.) and nullish coalescing (??) for safer property access.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/webviewer/src/adapter.ts` around lines 200 - 205, The error handling in handleDataApiResponse still directly reads resp.messages?.[0].code, which can throw on malformed or empty messages arrays before FileMakerError is raised. Update the message code access to use safe optional chaining and nullish coalescing throughout the FileMakerError branch so adapter.ts handles missing first entries without TypeError while preserving the intended fallback status code and message.Source: Coding guidelines
🧹 Nitpick comments (1)
packages/webviewer/src/adapter.ts (1)
391-392: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAvoid spreading page data in the accumulator loop.
records.push(...page.data)can hit argument-size limits for large pages; append records one-by-one while paginating.Proposed refactor
for (const page of pages) { - records.push(...(page.data ?? [])); + for (const record of page.data ?? []) { + records.push(record); + } }As per coding guidelines, avoid spread syntax in accumulators within loops.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/webviewer/src/adapter.ts` around lines 391 - 392, The accumulator loop in adapter.ts is using spread syntax to append page results, which can hit argument-size limits for large pages. Update the records-building logic in the pagination loop around the page iteration in the relevant adapter method to append each item individually instead of spreading page.data into records, keeping the same behavior while avoiding spread in the loop.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/webviewer/src/adapter.ts`:
- Around line 368-389: The pagination loop in paginateAll is using the raw
getBatchOptionsForRequest(batch)?.maxSize value, which can be NaN or Infinity
and break paging behavior. Normalize maxSize the same way as the enqueue/drain
path before assigning pageBatchSize, and keep the logic in adapter.ts aligned
with the existing batch-size normalization used elsewhere in this class.
---
Outside diff comments:
In `@packages/webviewer/src/adapter.ts`:
- Around line 200-205: The error handling in handleDataApiResponse still
directly reads resp.messages?.[0].code, which can throw on malformed or empty
messages arrays before FileMakerError is raised. Update the message code access
to use safe optional chaining and nullish coalescing throughout the
FileMakerError branch so adapter.ts handles missing first entries without
TypeError while preserving the intended fallback status code and message.
---
Nitpick comments:
In `@packages/webviewer/src/adapter.ts`:
- Around line 391-392: The accumulator loop in adapter.ts is using spread syntax
to append page results, which can hit argument-size limits for large pages.
Update the records-building logic in the pagination loop around the page
iteration in the relevant adapter method to append each item individually
instead of spreading page.data into records, keeping the same behavior while
avoiding spread in the loop.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 867aa169-7ac8-4965-8fc8-96e88e7573d3
📒 Files selected for processing (5)
.changeset/quick-bats-queue.mdapps/docs/content/docs/webviewer/batching.mdxapps/docs/content/docs/webviewer/fmdapi.mdxpackages/webviewer/src/adapter.tspackages/webviewer/tests/adapter.test.ts
✅ Files skipped from review due to trivial changes (3)
- .changeset/quick-bats-queue.md
- apps/docs/content/docs/webviewer/fmdapi.mdx
- apps/docs/content/docs/webviewer/batching.mdx
Summary:
batch: falsedisable coalescing while still sending single-request batch calls until legacy fallback is triggeredTest:
Summary by CodeRabbit
New Features
listAllandfindAllsupport for adapter-based pagination.Bug Fixes